<!DOCTYPE html>
<html>
<head>
<title>Electrostatic Equilibrium</title>
<style>
  body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    margin: 0;
    background-color: #f0f0f0;
  }
  canvas {
    border: 1px solid #999;
    background-color: white;
  }
</style>
</head>
<body>

<canvas id="physicsCanvas" width="550" height="500"></canvas>

<script>
const canvas = document.getElementById('physicsCanvas');
const ctx = canvas.getContext('2d');

// Helper function to draw an arrow on a path
function drawArrow(ctx, fromx, fromy, tox, toy) {
    const headlen = 8; // length of head in pixels
    const dx = tox - fromx;
    const dy = toy - fromy;
    const angle = Math.atan2(dy, dx);
    ctx.moveTo(fromx, fromy);
    ctx.lineTo(tox, toy);
    ctx.lineTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6));
    ctx.moveTo(tox, toy);
    ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6));
}

// Helper function to draw a double-headed arrow
function drawDoubleArrow(ctx, fromx, fromy, tox, toy) {
    ctx.beginPath();
    drawArrow(ctx, fromx, fromy, tox, toy);
    drawArrow(ctx, tox, toy, fromx, fromy);
    ctx.stroke();
}

// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.strokeStyle = 'black';
ctx.lineWidth = 1.5;
ctx.font = '16px Arial';

// Coordinates and Geometry
const pivot = { x: 270, y: 60 };
const sphereRadius = 20;
const horizontal_sep_pix = 80;
const thread_len_pix = 320;
const vertical_drop_pix = Math.sqrt(thread_len_pix**2 - horizontal_sep_pix**2);

const sphereX = { x: pivot.x, y: pivot.y + vertical_drop_pix };
const sphereY = { x: pivot.x + horizontal_sep_pix, y: pivot.y + vertical_drop_pix };


// --- Drawing ---

// 1. Ceiling support
ctx.beginPath();
ctx.moveTo(pivot.x - 140, pivot.y - 10);
ctx.lineTo(pivot.x + 140, pivot.y - 10);
ctx.stroke();
ctx.lineWidth = 1;
for (let i = -140; i < 140; i += 10) {
    ctx.beginPath();
    ctx.moveTo(pivot.x + i, pivot.y - 10);
    ctx.lineTo(pivot.x + i - 10, pivot.y - 20);
    ctx.stroke();
}
ctx.lineWidth = 1.5;


// 2. Sphere X and stand
// Stand
const standTopY = sphereX.y + sphereRadius;
const standBaseY = 440;
const baseWidth = 80;
ctx.beginPath();
ctx.moveTo(sphereX.x, standTopY);
ctx.lineTo(sphereX.x, standBaseY);
ctx.moveTo(sphereX.x - baseWidth / 2, standBaseY);
ctx.lineTo(sphereX.x + baseWidth / 2, standBaseY);
ctx.stroke();
ctx.beginPath();
ctx.rect(sphereX.x - baseWidth / 2 - 2, standBaseY, baseWidth + 4, 8);
ctx.fillStyle = 'white';
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'black';

// Sphere X
ctx.beginPath();
ctx.arc(sphereX.x, sphereX.y, sphereRadius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'black';


// 3. Sphere Y and thread
// Sphere Y
ctx.beginPath();
ctx.arc(sphereY.x, sphereY.y, sphereRadius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'black';

// Thread
ctx.beginPath();
ctx.moveTo(pivot.x, pivot.y);
ctx.lineTo(sphereY.x, sphereY.y);
ctx.stroke();


// 4. Dashed lines and dimensions
ctx.save();
ctx.setLineDash([4, 4]);
ctx.lineWidth = 1;

// Vertical reference line
ctx.beginPath();
ctx.moveTo(pivot.x, pivot.y);
ctx.lineTo(sphereX.x, sphereX.y);
ctx.stroke();

// Dashed lines for sphere Y position
ctx.beginPath();
ctx.moveTo(sphereX.x, sphereY.y);
ctx.lineTo(sphereY.x, sphereY.y);
const vertDashEnd = sphereY.y + 40;
ctx.moveTo(sphereY.x, sphereY.y);
ctx.lineTo(sphereY.x, vertDashEnd);
ctx.stroke();

// Dashed lines for length dimension
ctx.beginPath();
const threadVec = { x: sphereY.x - pivot.x, y: sphereY.y - pivot.y };
const pivotExt = { x: pivot.x - 0.1 * threadVec.x, y: pivot.y - 0.1 * threadVec.y };
const sphereYExt = { x: sphereY.x + 0.1 * threadVec.x, y: sphereY.y + 0.1 * threadVec.y };
ctx.moveTo(pivotExt.x, pivotExt.y);
ctx.lineTo(pivot.x, pivot.y);
ctx.moveTo(sphereY.x, sphereY.y);
ctx.lineTo(sphereYExt.x, sphereYExt.y);
ctx.stroke();

ctx.restore();

// Horizontal dimension line
const dimY = sphereY.y + 40;
ctx.beginPath();
ctx.moveTo(sphereX.x, dimY - 5);
ctx.lineTo(sphereX.x, dimY + 5);
ctx.moveTo(sphereY.x, dimY - 5);
ctx.lineTo(sphereY.x, dimY + 5);
ctx.moveTo(sphereX.x, dimY);
ctx.lineTo(sphereY.x, dimY);
ctx.stroke();


// 5. Labels and text
// Labels with pointers
ctx.textAlign = 'left';
ctx.fillText('vertical', 170, 150);
ctx.fillText('line', 170, 170);
ctx.beginPath();
ctx.moveTo(215, 160);
ctx.lineTo(pivot.x - 5, 160);
ctx.stroke();

ctx.fillText('thread', 180, 250);
ctx.beginPath();
ctx.moveTo(230, 258);
ctx.lineTo(pivot.x + 0.25 * threadVec.x, pivot.y + 0.25 * threadVec.y);
ctx.stroke();

// Sphere and stand labels
ctx.textAlign = 'right';
ctx.fillText('sphere X', sphereX.x - 50, sphereX.y);
ctx.fillText('charge +96 nC', sphereX.x - 50, sphereX.y + 20);
ctx.fillText('stand', sphereX.x - 50, standBaseY);

ctx.textAlign = 'left';
ctx.fillText('sphere Y', sphereY.x + 30, sphereY.y);
ctx.fillText('charge +64 nC', sphereY.x + 30, sphereY.y + 20);

// Dimension values
ctx.textAlign = 'center';
ctx.fillText('0.080 m', (sphereX.x + sphereY.x) / 2, dimY - 10);

// Length dimension
const offset = 40;
const perpVec = { x: threadVec.y, y: -threadVec.x };
const perpVecMag = Math.sqrt(perpVec.x**2 + perpVec.y**2);
const perpUnitVec = { x: perpVec.x / perpVecMag, y: perpVec.y / perpVecMag };
const arrow_from = {x: pivot.x + offset * perpUnitVec.x, y: pivot.y + offset * perpUnitVec.y};
const arrow_to = {x: sphereY.x + offset * perpUnitVec.x, y: sphereY.y + offset * perpUnitVec.y};
drawDoubleArrow(ctx, arrow_from.x, arrow_from.y, arrow_to.x, arrow_to.y);

ctx.save();
ctx.translate((arrow_from.x + arrow_to.x) / 2, (arrow_from.y + arrow_to.y) / 2);
ctx.rotate(Math.atan2(threadVec.y, threadVec.x));
ctx.fillText('1.2 m', 0, -10);
ctx.restore();

// Figure caption
ctx.fillText('Fig. 4.1', canvas.width / 2, 480);

</script>

</body>
</html>